﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using AccessControl.HardwareLibrary.EnumAndStruct;
using AccessControl.Utility.Tools;

namespace AccessControl.HardwareLibrary.FS84
{
    public struct FingerPrintDetectedEventArgs
    {
        public int contrast;
        public byte dosage;
        public byte[] rawImage;
        public int whitePixels;
    }

    public class FS84Communicator : Device
    {
        #region Delegates

        public delegate void FingerPrintDetectedEventHandler(object sender, FingerPrintDetectedEventArgs args);

        #endregion

        public int curRow; //delete this

        #region Events

        public event FingerPrintDetectedEventHandler FingerPrintDetected;
        public event EventHandler WaitTimeoutExpired;
        public event EventHandler ConnectionError;
        
        public void OnConnectionError(EventArgs e)
        {
            if (ConnectionError != null) ConnectionError(this, e);
        }

        #endregion

        #region fields
        private int timeoutStart;
        private int timeoutLimit;

        private int fingerChecksCount;
        private Socket _socket;
        // ReSharper disable UnaccessedField.Local
        private int checkInterval = 100;
        // ReSharper restore UnaccessedField.Local
        private bool isSendReceive;
        public bool isWaitingForFinger { get; set; }
        private bool isInWaitingLoop;
        private bool isbusy;
        private int lastFingerDetectedTime;
        private Thread waitThread;
        #endregion

        #region properties

        private bool _autoReconnect=true;
        public bool AutoReconnect{
            get { return _autoReconnect; }
            set { _autoReconnect = value; }
        }

        public override string DeviceType
        {
            get { return "FS84"; }
        }

        public bool EnableIdleOptimization { get; set; }
        public ErrorCodes LastErrorCode { get; set; }
        public string lastErrorMessage { get; set; }

        #endregion

        #region constructors

        //public FS84Commiunicator(string ipString, int port)
        //    : this()
        //{
        //    Connect(ipString, port);
        //}

        #endregion

        #region methods

        public bool CheckFinger(out int contrast)
        {
            var tempParam2 = 0;
            contrast = 0;
            var retVal = (LastErrorCode =
                           CommiunicateWithFAC(Commands.CheckFinger, ref contrast, ref tempParam2, 0x00, null, null)) ==
                          ErrorCodes.RESULT_OK;

            if (retVal)
            {
                lastFingerDetectedTime = Environment.TickCount;
                checkInterval = 100;
            }
            else
            {
                if (EnableIdleOptimization)
                {
                    if ((Environment.TickCount-lastFingerDetectedTime) < 60000)
                        checkInterval = 300;
                    else if ((Environment.TickCount - lastFingerDetectedTime) < 600000)
                        checkInterval = 500;
                    else if ((Environment.TickCount - lastFingerDetectedTime) < 3600000)
                        checkInterval = 1000;
                    else
                        checkInterval = 1000;
                }
                else checkInterval = 30;
            }

            return retVal;
        }

        public bool CaptureImage(out int contrast, out int whitePixels, out byte dosage)
        {
            var retVal = false;
            contrast = 0;
            whitePixels = 0;
            var command = (byte)Commands.CaptureImage;
            retVal = (LastErrorCode = CommiunicateWithFAC(ref command, ref contrast, ref whitePixels, 0x00, null, null)) ==
                     ErrorCodes.RESULT_OK;
            dosage = command;
            return retVal;
        }


        public byte[] CaptureAndDownloadImage(bool checkFinger, out int contrast, out int whitePixels, out byte dosage)
        {
            while (isbusy) Thread.Sleep(0);
            isbusy = true;

            whitePixels = 0;
            // ReSharper disable RedundantAssignment
            contrast = 0;
            // ReSharper restore RedundantAssignment
            dosage = 0;
            byte[] rawImage = null;
            if (!checkFinger || CheckFinger(out contrast))
            {
                CaptureImage(out contrast, out whitePixels, out dosage);

                if (contrast > 1000)
                {
                    Beep(Beeps.Normal, false);

                    rawImage = DownloadRawImage();
                }
            }

            isbusy = false;

            return rawImage;
        }

        private byte[] DownloadRawImage()
        {
            const int imageWidth = 320;
            const int imageHeight = 480;
            const int totalBytes = imageWidth * imageHeight;
            var RAWImage = new byte[totalBytes];


            var param1 = 0;
            var param2 = totalBytes;

            if (
                (LastErrorCode =
                 CommiunicateWithFAC(Commands.DownloadRAWImage, ref param1, ref param2, 0x00, null, RAWImage)) ==
                ErrorCodes.RESULT_OK)
            {
                //nothing, if false the rawimage would be null
            }

            return RAWImage;
        }

        public byte[] DownloadCurrentSample()
        {
            const int imageWidth = 320;
            const int imageHeight = 480;
            //var totalBytes = imageWidth*imageHeight;
            var RAWImage = new byte[665];


            var param1 = 0;
            var param2 = 0;

            if (
                (LastErrorCode =
                 CommiunicateWithFAC(Commands.DownloadCurrentSample, ref param1, ref param2, 0x00, null, RAWImage)) ==
                ErrorCodes.RESULT_OK)
            {
                //nothing, if false the rawimage would be null
            }

            return RAWImage;
        }

        public void WaitForFingerprint(int timeout)
        {
            isWaitingForFinger = true;
            timeoutStart = Environment.TickCount;
            timeoutLimit = timeout;
            if (isInWaitingLoop) return;
            waitThread = new Thread(WaitForFingerprint);
            waitThread.Start();
        }

        public void StopWaitingForFinger()
        {
            isWaitingForFinger = false;
        }

        private void WaitForFingerprint(object data){
            isInWaitingLoop = true;
            var args = new FingerPrintDetectedEventArgs();
            var Retry = true;
            while (Retry){
                fingerChecksCount++;
                //System.Diagnostics.Debug.WriteLine(fingerChecksCount);

                args.rawImage = CaptureAndDownloadImage(true, out args.contrast, out args.whitePixels, out args.dosage);

                Retry = isWaitingForFinger && args.rawImage == null;

                if (Retry) Thread.Sleep(checkInterval);

                if (timeoutLimit > 0 && Environment.TickCount > timeoutLimit+ timeoutStart && isWaitingForFinger)
                {
                    isWaitingForFinger = false;
                    WaitTimeoutExpired(this, EventArgs.Empty);
                    
                    if(!isWaitingForFinger){
                        isInWaitingLoop = false;
                        return;
                    }
                }
            }


            if (isWaitingForFinger)
            {
                isInWaitingLoop = false;
                isWaitingForFinger = false;
                FingerPrintDetected(this, args);
            }
        }

        public void BeepAsync(Beeps beepType)
        {
            var thBeep = new Thread(new ParameterizedThreadStart(Beep));
            thBeep.Start(beepType);
        }

        private void Beep(object beepType)
        {
            Beep((Beeps)beepType);
        }

        public void Beep(Beeps beepType)
        {
            Beep(beepType, true);
        }

        private void Beep(Beeps beepType, bool setBusy)
        {
            if (setBusy)
            {
                while (isbusy) Thread.Sleep(0);
                isbusy = true;
            }


            var repeats = 0;
            var duration = 30;
            var delay = 30;
            switch (beepType)
            {
                case Beeps.Normal:
                    repeats = 1;
                    duration = 30;
                    break;
                case Beeps.DoubleShort:
                    repeats = 2;
                    delay = 50;
                    duration = 50;
                    break;
                case Beeps.DoubleLong:
                    repeats = 2;
                    delay = 200;
                    duration = 300;
                    break;
                case Beeps.Triple:
                    repeats = 3;
                    delay = 80;
                    duration = 50;
                    break;
                case Beeps.TestLong:
                    repeats = 4;
                    delay = 1000;
                    duration = 100;
                    break;
            }
            for (var r = 1; r <= repeats; r++)
            {
                SwitchDevicePeripheral(DevicePeripherals.Buzzer, true);
                Thread.Sleep(duration);
                SwitchDevicePeripheral(DevicePeripherals.Buzzer, false);
                if (r < repeats) Thread.Sleep(delay);
            }

            if (setBusy) isbusy = false;
        }

        public bool SwitchDevicePeripheral(DevicePeripherals peripheral, bool IsOn)
        {
            bool param1, param2;
            return SwitchDevicePeripheral(peripheral, IsOn, out param1, out param2);
        }

        public bool SwitchDevicePeripheral(DevicePeripherals peripheral, bool IsOn, out bool DoorSensor0,
                                           out bool DoorSensor1)
        {
            var retVal = false;

            var param1 = (int)(IsOn ? peripheral : 0x00000000);
            var param2 = (int)(IsOn ? 0x00000000 : peripheral);

            DoorSensor0 = false;
            DoorSensor1 = false;

            if (
                (LastErrorCode =
                 CommiunicateWithFAC(Commands.SwitchPeripheral, ref param1, ref param2, 0x00, null, null)) ==
                ErrorCodes.RESULT_OK)
            {
                retVal = true;
                DoorSensor0 = (param1 & 1) > 0;
                DoorSensor1 = (param1 & 2) > 0;
            }

            return retVal;
        }

        public void CancelCommand()
        {
            var param1 = 0;
            var param2 = 0;
            CommiunicateWithFAC(Commands.Cancel, ref param1, ref param2, 0x00, null, null);
        }

        public int GetFreeMemory()
        {
            var param1 = 0;
            var param2 = 0;
            CommiunicateWithFAC(Commands.GetFreeMemory, ref param1, ref param2, 0x00, null, null);
            return param1;
        }

        public void EraseAllTemplates()
        {
            var param1 = 0;
            var param2 = 0;
            CommiunicateWithFAC(Commands.EraseAllTemplates, ref param1, ref param2, 0x00, null, null);
        }

        public void Reboot()
        {
            _socket.Send(new byte[]{
                                       0x40, (byte) Commands.Reboot, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                       0x00, 0x3F, 0x0D
                                   });
            //while (_socket.Connected) { Thread.Sleep(500); }
            Connect();
        }

        public int ProcessCapturedSample()
        {
            var param1 = 0;
            var param2 = 0;
            CommiunicateWithFAC(Commands.ProcessCapturedFinger, ref param1, ref param2, 0x00, null, null);
            return param1;
        }

        public ErrorCodes CommiunicateWithFAC(Commands Command, ref int param1, ref int param2, byte flag,
                                              byte[] SendBuffer, byte[] ReceiveBuffer)
        {
            var command = (byte)Command;
            return CommiunicateWithFAC(ref command, ref param1, ref param2, flag, SendBuffer, ReceiveBuffer);
        }

        public ErrorCodes CommiunicateWithFAC(ref byte Command, ref int param1, ref int param2, byte flag,
                                              byte[] SendBuffer, byte[] ReceiveBuffer)
        {
            while (isSendReceive) Thread.Sleep(0);
            isSendReceive = true;

            ErrorCodes retVal;

            var successed = false;
            var retries = 0;
            var param = new byte[13];
            var buffer = new byte[13];
            param[0] = 0x40;
            param[1] = Command;
            BitConverter.GetBytes(param1).CopyTo(param, 2);
            BitConverter.GetBytes(param2).CopyTo(param, 6);
            param[10] = flag;
            param[11] = CalculateChecksum(param);
            param[12] = 0x0D;
            do
            {
                retries++;
                if (Send(param, param.Length, 3000))
                {
                    if (Receive(buffer, buffer.Length, 3000))
                    {
                        if ((ErrorCodes)buffer[0] == ErrorCodes.RESULT_OK)
                        {
                            param1 = BitConverter.ToInt32(buffer, 2);
                            param2 = BitConverter.ToInt32(buffer, 5);

                            if (CalculateChecksum(buffer) == buffer[11])
                            {
                                successed = true;
                            }
                        }
                    }
                }
                else
                    return retVal = ErrorCodes.RESULT_WINSOCK_ERROR;
            } while (!successed && retries < 3);

            if (successed)
            {
                var eof = new byte[2];
                switch ((Commands)Command)
                {
                    case Commands.DownloadRAWImage:

                        if (Receive(ReceiveBuffer, ReceiveBuffer.Length, 5000))
                        {
                            Receive(eof, eof.Length, 2000);
                            var CRCError = CalculateChecksum(ReceiveBuffer) != eof[0];
                            successed = !CRCError;
                        }
                        break;
                    case Commands.DownloadCurrentSample:
                        if (Receive(ReceiveBuffer, ReceiveBuffer.Length, 5000))
                        {
                            Receive(eof, 1, 2000);
                            //var CRCError = CalculateChecksum(ReceiveBuffer) != eof[0];
                            //successed = !CRCError;
                            successed = true;
                        }
                        break;
                }

                Command = buffer[1];
            }

            if (!successed) retVal = ErrorCodes.RESULT_WINSOCK_ERROR;
            else retVal = (ErrorCodes)buffer[10];

            isSendReceive = false;
            return retVal;
        }

        private bool Connect(){
            return Connect(AutoReconnect);
        }

        private bool Connect(bool autoReconnect)
        {
            while (_socket == null || (!_socket.Connected && autoReconnect))
            {
                try
                {
                    //if(_socket==null || !((IPEndPoint)_socket.RemoteEndPoint).Address.Equals(IP.Address))
                    _socket = new Socket(IP.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
                    _socket.Connect(IP);
                }
                catch (Exception ex)
                {
                    WriteLog("Error connecting to device," + ex.Message, eventLevelConstants.Error);
                    lastErrorMessage = ex.Message;
                    if (!AutoReconnect) return false;
                    Thread.Sleep(10000);
                }
                OnConnectionChanged(true);
                WriteLog("Connected", eventLevelConstants.Info);
            }
            return _socket.Connected;
        }

        public override bool Connect(string ipString, int port, int failRetries)
        {
            if (_socket == null || !_socket.Connected)
            {
                var ipAddress = IPAddress.Parse(ipString);
                IP = new IPEndPoint(ipAddress, port);

                var retries = 0;
                while (!Connect(false))
                {
                    if (failRetries == -1 || ++retries < failRetries){
                        Thread.Sleep(3000);
                        continue;
                    }
                    if (failRetries >= 0) WriteLog("Unable to connect to device after " + retries + " attempt(s)!", eventLevelConstants.Error);
                    return false;
                }
                return true;
            }
            return true;
        }

        private static byte CalculateChecksum(byte[] array)
        {
            var checksum = 0;
            for (var i = 0; i < 11; i++)
                checksum += array[i];
            return (byte)(checksum % 256);
        }

        private bool Send(byte[] buffer, int size, int timeout)
        {
            FlushData();

            var startTickCount = Environment.TickCount;
            var sent = 0; // how many bytes is already sent
            do
            {
                try
                {
                    if (timeout > 0)
                        _socket.ReceiveTimeout = Math.Max(0, timeout - (Environment.TickCount - startTickCount));
                    if (timeout > 0 && Environment.TickCount > startTickCount + timeout)
                        break;

                    sent += _socket.Send(buffer, sent, size - sent, SocketFlags.None);
                }
                catch (SocketException ex)
                {
                    switch (ex.SocketErrorCode)
                    {
                        case SocketError.NoBufferSpaceAvailable:
                        case SocketError.IOPending:
                        case SocketError.WouldBlock:
                            Thread.Sleep(30);
                            break;
                        case SocketError.NetworkReset:
                        case SocketError.ConnectionReset:
                            Thread.Sleep(30);
                            OnConnectionChanged(false);
                            Connect();
                            break;
                        case SocketError.TimedOut:
                            break;
                        case SocketError.Interrupted:
                            break;
                        default:
                            break;
                    }
                }
            } while (sent < size);

            return true;
        }


        private bool Receive(byte[] buffer, int size, int timeout)
        {
            var retVal = false;

            var startTickCount = Environment.TickCount;
            var received = 0; // how many bytes is already received
            do
            {
                try
                {
                    if (timeout > 0)
                        _socket.ReceiveTimeout = timeout; // Math.Max(0, timeout - (Environment.TickCount - startTickCount));

                    //while (_socket.Available == 0 && !(timeout > 0 && Environment.TickCount > startTickCount + timeout))
                    //{
                    //    Thread.Sleep(100);
                    //}
                    //if (timeout > 0 && Environment.TickCount > startTickCount + timeout)
                    //{
                    //    //_socket.Send(new byte[] { 0x40, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8C, 0x0D });
                    //    //_socket.Receive(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
                    //    if (_socket.Available == 0) 
                    //        break;
                    //}
                    received += _socket.Receive(buffer, received, size - received, SocketFlags.None);
                    if (received < size)
                        SendKeepAlive();
                    else
                        retVal = true;
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
                        ex.SocketErrorCode == SocketError.IOPending ||
                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                    {
                        // socket buffer is probably empty, wait and try again
                        Thread.Sleep(30);
                    }
                    else if (ex.SocketErrorCode == SocketError.ConnectionReset ||
                             ex.SocketErrorCode == SocketError.NetworkReset)
                    {
                        Thread.Sleep(5000);
                        OnConnectionChanged(false);
                        Connect();
                    }
                    else if (ex.SocketErrorCode == SocketError.TimedOut)
                        break;
                    else if (ex.SocketErrorCode == SocketError.Interrupted)
                        break;
                    else
                        break;
                }
            } while (received < size);

            if (_socket.Available != 2 && size > 13)
            {
                var m = _socket.Available;
                var b = 2;
            }

            return retVal;
        }

        private void SendKeepAlive()
        {
            Send(new byte[] { 0 }, 1, 0);
        }

        public void FlushData()
        {
            var buffer = new byte[1000];
            while (_socket.Available > 0)
                _socket.Receive(buffer);
        }

        public override void Disconnect()
        {
            StopWaitingForFinger();
            if (waitThread!=null) waitThread.Abort();
            if (_socket.Connected)
                _socket.Disconnect(false);

            OnConnectionChanged(false);
        }

        #endregion
    }
}